<?php 
// +----------------------------------------------------------------------
// | ThinkPHP                                                             
// +----------------------------------------------------------------------
// | Copyright (c) 2008 http://thinkphp.cn All rights reserved.      
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>                                  
// +----------------------------------------------------------------------
// $Id$

// 压缩和解压缩实现类
class Zip extends Base {

	// 配置参数
	static public $config	=	array();

	// 初始化
	static public function init($config=array()) {
		if(empty(self::$config["init"])){
			self::$config["DataSec"]      = array();
			self::$config["Ctrl_Dir"]     = array();
			self::$config["Eof_Ctrl_Dir"] = "\x50\x4b\x05\x06\x00\x00\x00\x00";
			self::$config["Old_Offset"]   = 0;
			self::$config["init"]  = 1;
			return true;
		}else{ 
			return false; 
		}
	}

	//增加压缩文件
	static public function addFile($FileData,$FileName,$Times = 0){
		$FileName     = str_replace("\\","/",$FileName);

		$DTime        = dechex(self::_unix2DosTime($Times));
		$HexdTime     = "\x" . $DTime[6] . $DTime[7] . "\x" . $DTime[4] . $DTime[5] . "\x" . $DTime[2] . $DTime[3] . "\x" . $DTime[0] . $DTime[1];
		eval('$HexdTime = "' . $HexdTime . '";');

		$Fr    = "\x50\x4b\x03\x04";
		$Fr   .= "\x14\x00";
		$Fr   .= "\x00\x00";
		$Fr   .= "\x08\x00";
		$Fr   .= $HexdTime;

		//本地文件头信息
		$UNC_Len = strlen($FileData);
		$CRC     = crc32($FileData);
		$ZData   = gzcompress($FileData);
		$C_Len   = strlen($ZData);
		$ZData   = substr(substr($ZData,0,strlen($ZData) - 4), 2);
		$Fr     .= pack("V", $CRC);
		$Fr     .= pack("V", $C_Len);
		$Fr     .= pack("V", $UNC_Len);
		$Fr     .= pack("v", strlen($FileName));
		$Fr     .= pack("v", 0);
		$Fr     .= $FileName;

		//文件数据
		$Fr .= $ZData;

		//文件描述
		$Fr .= pack("V", $CRC);
		$Fr .= pack("V", $C_Len);
		$Fr .= pack("V", $UNC_Len);

		//增加文件信息到压缩数据
		self::$config["DataSec"][] = $Fr;
		$New_Offset	      = strlen(implode("",self::$config["DataSec"]));

		//加入压缩目录数据
		$Cdrec  = "\x50\x4b\x01\x02";
		$Cdrec .= "\x00\x00";
		$Cdrec .= "\x14\x00";
		$Cdrec .= "\x00\x00";
		$Cdrec .= "\x08\x00";
		$Cdrec .= $HexdTime;
		$Cdrec .= pack("V", $CRC);
		$Cdrec .= pack("V", $C_Len);
		$Cdrec .= pack("V", $UNC_Len);
		$Cdrec .= pack("v", strlen($FileName));
		$Cdrec .= pack("v", 0);
		$Cdrec .= pack("v", 0);
		$Cdrec .= pack("v", 0);
		$Cdrec .= pack("v", 0);
		$Cdrec .= pack("V", 32);
		$Cdrec .= pack("V", self::$config["Old_Offset"]);

		self::$config["Old_Offset"]  = $New_Offset;

		$Cdrec .= $FileName;

		// 保存压缩目录信息
		self::$config["Ctrl_Dir"][] = $Cdrec;
	}

	//增加压缩目录
	static public function addDir($DirName){
		if (@file_exists($DirName)){
			if(!is_array($DirName)){
				$DirHead = opendir($DirName);
				while($Files = readdir($DirHead)){
					switch (true){
						case ($Files == "."):
							break;
						case ($Files == ".."):
							break;
						case (is_dir($DirName."/".$Files)):
							self::addDir($DirName."/".$Files);
							break;
						default:
							$FileName    = $DirName."/".$Files;
							$FileHead    = fopen($FileName,"r");
							$FileContent = fread($FileHead,filesize($FileName)+1);
							fclose($FileHead);
							self::addFile($FileContent,$FileName);
							break;
					}
				}
				closedir($DirHead);
			}
		}
	}

	//创建Zip文件
	static public function create($ZipFileName){
		//获取ZIP数据
		$FileContent = self::_getZipData();
		//建立ZIP文件
		$FileHead    = fopen($ZipFileName,"wb");
		fwrite($FileHead,$FileContent,strlen($FileContent));
		fclose($FileHead);
	}

	//解压Zip文件
	static public function extract($ZipName,$ToDir="",$Index=Array(-1)){
		$OK  = 0;
		$Zip = @fopen($ZipName,"rb");

		if(!$Zip){ return(-1); }

		$CDir      = self::_readCentralDir($Zip,$ZipName);
		$Pos_Entry = $CDir["offset"];

		if(!is_array($Index)){ $Index = array($Index);  }
		for($i=0; $Index[$i];$i++){
			if(intval($Index[$i])!=$Index[$i] or $Index[$i]>$CDir["entries"])
			return(-1);
		}

		for($i=0; $i<$CDir["entries"]; $i++){
			@fseek($Zip, $Pos_Entry);
			$Headers          = self::_readCentralFileHeaders($Zip);
			$Headers["index"] = $i;
			$Pos_Entry        = ftell($Zip);

			@rewind($Zip);
			fseek($Zip, $Headers["offset"]);

			if(in_array("-1",$Index) or in_array($i,$Index)){
				$Stat[$Headers["filename"]] = self::_extractFile($Headers,$ToDir,$Zip);
			}
		}

		fclose($Zip);
		return $Stat;
	}

	//清空数据
	static public function clear(){
		//还原数据内容
		self::$config["DataSec"]      = array();
		self::$config["Ctrl_Dir"]     = array();
		self::$config["Eof_Ctrl_Dir"] = "\x50\x4b\x05\x06\x00\x00\x00\x00";
		self::$config["Old_Offset"]   = 0;
	}

	//关闭PHPZip
	static public function close(){
		if(self::$config["init"]){
			unset(self::$config["DataSec"],self::$config["Ctrl_Dir"],self::$config["Eof_Ctrl_Dir"],self::$config["Old_Offset"],self::$config["Dirs"],self::$config["init"]);
			return 1;
		}else{
			return 0;
		}
	}

	//转化unix时间为dos时间
	static private function _unix2DosTime($Unixtime= 0){
		$TimeArray = ($Unixtime== 0) ? getdate() : getdate($Unixtime);

		if ($TimeArray["year"] < 1980) {
			$TimeArray["year"]    = 1980;
			$TimeArray["mon"]     = 1;
			$TimeArray["mday"]    = 1;
			$TimeArray["hours"]   = 0;
			$TimeArray["minutes"] = 0;
			$TimeArray["seconds"] = 0;
		}
		return (($TimeArray["year"] - 1980) << 25) | ($TimeArray["mon"] << 21) | ($TimeArray["mday"] << 16) | ($TimeArray["hours"] << 11) | ($TimeArray["minutes"] << 5) | ($TimeArray["seconds"] >> 1);
	}

	//读取ZIP文件头
	static private function _readFileHeader($Zip){
		$Binary_Data = fread($Zip,30);
		$Data = unpack("vchk/vid/vversion/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len", $Binary_Data);

		$Headers["filename"] = fread($Zip,$Data["filename_len"]);
		if($Data["extra_len"] != 0){
			$Headers["extra"] = fread($Zip,$Data["extra_len"]);
		}else{ $Headers["extra"] = ""; }

		$Headers["compression"]     = $Data["compression"];
		$Headers["size"]            = $Data["size"];
		$Headers["compressed_size"] = $Data["compressed_size"];
		$Headers["crc"]             = $Data["crc"];
		$Headers["flag"]            = $Data["flag"];
		$Headers["mdate"]           = $Data["mdate"];
		$Headers["mtime"]           = $Data["mtime"];

		if ($Headers["mdate"] and $Headers["mtime"]){
			$Hour    = ($Headers["mtime"]&0xF800)>>11;
			$Minute  = ($Headers["mtime"]&0x07E0)>>5;
			$Seconde = ($Headers["mtime"]&0x001F)*2;
			$Year    = (($Headers["mdate"]&0xFE00)>>9)+1980;
			$Month   = ($Headers["mdate"]&0x01E0)>>5;
			$day     = $Headers["mdate"]&0x001F;

			$Headers["mtime"] = mktime($Hour,$Minute,$Seconde,$Month,$Day,$Year);
		}else{ $Headers["mtime"] = time(); }

		$Headers["stored_filename"] = $Headers["filename"];
		$Headers["status"] = "ok";
		return $Headers;
	}

	//读取文件内容头
	static private function _readCentralFileHeaders($Zip){
		$Binary_Data = fread($Zip,46);
		$Headers     = unpack("vchkid/vid/vversion/vversion_extracted/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len/vcomment_len/vdisk/vinternal/Vexternal/Voffset", $Binary_Data);

		if($Headers["filename_len"] != 0){
			$Headers["filename"] = fread($Zip,$Headers["filename_len"]);
		}else{ $Headers["filename"] = ""; }

		if($Headers["extra_len"] != 0){
			$Headers["extra"] = fread($Zip, $Headers["extra_len"]);
		}else{ $Headers["extra"] = ""; }

		if($Headers["comment_len"] != 0){
			$Headers["comment"] = fread($Zip, $Headers["comment_len"]);
		}else{ $Headers["comment"] = ""; }

		if($Headers["mdate"] && $Headers["mtime"]){
			$Hour    = ($Headers["mtime"] & 0xF800) >> 11;
			$Minute  = ($Headers["mtime"] & 0x07E0) >> 5;
			$Seconde = ($Headers["mtime"] & 0x001F)*2;
			$Year    = (($Headers["mdate"] & 0xFE00) >> 9) + 1980;
			$Month   = ($Headers["mdate"] & 0x01E0) >> 5;
			$Day     = $Headers["mdate"] & 0x001F;

			$Headers["mtime"] = mktime($Hour,$Minute,$Seconde,$Month,$Day,$Year);
		}else{ $Headers["mtime"] = time(); }

		$Headers["stored_filename"] = $Headers["filename"];
		$Headers["status"] = "ok";
		if(substr($Headers["filename"], -1) == "/"){ $Headers["external"] = 0x41FF0010; }

		return $Headers;
	 }

	//读取目录内容
	static private function _readCentralDir($Zip,$ZipName){
		$Size = filesize($ZipName);
		if($Size < 277){
			$Maximum_Size = $Size;
		}else{ $Maximum_Size = 277; }

		@fseek($Zip,$Size-$Maximum_Size);
		$pos   = ftell($Zip);
		$bytes = 0x00000000;

		while($pos < $Size){
			$byte  = @fread($Zip,1);
			$bytes = ($bytes << 8) | ord($byte);
			if($bytes == 0x504b0506){ $pos++; break; }
			$pos++;
		}

		$Data=unpack("vdisk/vdisk_start/vdisk_entries/ventries/Vsize/Voffset/vcomment_size",fread($Zip,18));

		if($Data["comment_size"] != 0){
			$Centd["comment"] = fread($Zip, $Data["comment_size"]);
		}else{ $Centd["comment"] = ""; $Centd["entries"] = $Data["entries"]; }

		$Centd["disk_entries"] = $Data["disk_entries"];
		$Centd["offset"]       = $Data["offset"];
		$Centd["disk_start"]   = $Data["disk_start"];
		$Centd["size"]         = $Data["size"];
		$Centd["disk"]         = $Data["disk"];

		return $Centd;
	}


	//解压文件
	static private function _extractFile($Headers,$ToDir,$Zip){
		//返回文件内容&属性
		$Headers = self::_readFileHeader($Zip);

		//判断目录尾是否有/存在
		if($ToDir!="" and substr($ToDir,-1)!="/"){ $ToDir.="/"; }

		//判断目录是否存在
		if($ToDir!="" and !is_dir($ToDir)){ @mkdir($ToDir,0777); }


		//获取所有子目录
		$Pth=explode("/",dirname($Headers["filename"]));

		//创建子目录完整路径
		$Pths="";

		//建立子目录
		$Pths=$Pth[0];
		if(!is_dir($ToDir.$Pths)){ @mkdir($ToDir.$Pths,0777); }
		for($i=1;isset($Pth[$i]);$i++){
			$Pths.="/".$Pth[$i];
			if(!$Pth[$i]){ break; }
			if(!is_dir($ToDir.$Pths)){ @mkdir($ToDir.$Pths,0777); }
		}

		//建立文件
		if (!($Headers["external"]==0x41FF0010) and !($Headers["external"]==16)){
			$ZipFileName=$ToDir.$Headers["filename"];
			if($Headers["compression"]==0){
				$FileHead = @fopen($ZipFileName,"wb");
				if(!$FileHead){ return(-1); }
				$Size = $Headers["compressed_size"];

				while($Size != 0){
					$Read_Size   = ($Size > 2048) ? $Size : 2048;
					$Buffer      = fread($Zip,$Read_Size);
					$Binary_Data = pack("a".$Read_Size,$Buffer);

					@fwrite($FileHead,$Binary_Data);
					$Size -= $Read_Size;
				}
				fclose($FileHead);
				touch($ZipFileName,$Headers["mtime"]);
			}else{
				$FileHead = @fopen($ZipFileName.".gz","wb");
				if(!$FileHead){ return(-1); }
				$Binary_Data = pack("va1a1Va1a1",0x8b1f,chr($Headers["compression"]),chr(0x00),time(),chr(0x00),chr(3));

				fwrite($FileHead,$Binary_Data,10);
				$Size = $Headers["compressed_size"];

				while($Size != 0){
					$Read_Size   = ($Size < 1024) ? $Size : 1024;
					$Buffer      = fread($Zip,$Read_Size);
					$Binary_Data = pack("a".$Read_Size,$Buffer);

					@fwrite($FileHead,$Binary_Data,$Read_Size);
					$Size -= $Read_Size;
				}

				$Binary_Data = pack("VV", $Headers["crc"], $Headers["size"]);
				fwrite($FileHead, $Binary_Data,8);
				fclose($FileHead);

				$Gzp = @gzopen($ZipFileName.".gz","rb");
				if(!$Gzp){ return(-2); }
				$FileHead = @fopen($ZipFileName,"wb");
				if(!$FileHead){ return(-1); }
				$Size = $Headers["size"];

				while($Size != 0){
					$Read_Size = ($Size < 2048) ? $Size : 2048;
					$Buffer = gzread($Gzp,$Read_Size);
					$Binary_Data = pack("a".$Read_Size,$Buffer);
					@fwrite($FileHead,$Binary_Data,$Read_Size);
					$Size -= $Read_Size;
				}
				fclose($FileHead);
				gzclose($Gzp);

				touch($ZipFileName,$Headers["mtime"]);
				@unlink($ZipFileName.".gz");
			}
		}
		return true;
	}

	//获取压缩内容列表
	static private function _getList($ZipName){
		$Zip= @fopen($ZipName,"rb");
		if(!$Zip){ return(0); }
		$Centd = self::_readCentralDir($Zip,$ZipName);

		@rewind($Zip);
		@fseek($Zip,$Centd["offset"]);

		for ($i=0;$i<$Centd["entries"];$i++){
			$Headers = self::_readCentralFileHeaders($Zip);

			$Headers["index"]        = $i;
			$Info["filename"]        = $Headers["filename"];
			$Info["stored_filename"] = $Headers["stored_filename"];
			$Info["size"]            = $Headers["size"];
			$Info["compressed_size"] = $Headers["compressed_size"];
			$Info["crc"]             = strtoupper(dechex($Headers["crc"]));
			$Info["mtime"]           = $Headers["mtime"];
			$Info["comment"]         = $Headers["comment"];
			$Info["folder"]          = ($Headers["external"]==0x41FF0010 or $Headers["external"]==16)?1:0;
			$Info["index"]           = $Headers["index"];
			$Info["status"]          = $Headers["status"];
			$Ret[]                   = $Info;
		}
		return $Ret;
	}

	//获取zip数据
	static private function _getZipData(){
		$Data     = implode("",self::$config["DataSec"]);
		$CtrlDir  = implode("",self::$config["Ctrl_Dir"]);
		$DirSize  = sizeof(self::$config["Ctrl_Dir"]);

		return $Data.$CtrlDir.self::$config["Eof_Ctrl_Dir"].pack("v",$DirSize).pack("v",$DirSize).pack("V",strlen($CtrlDir)).pack("V",strlen($Data))."\x00\x00";
	}
}
?>